package cn.iocoder.yudao.module.written.exam.service.writtenexam;

import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.enums.WrittenExamTypeEnum;
import cn.iocoder.yudao.framework.common.enums.YesOrNoEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.infra.util.file.FileUploadUtils;
import cn.iocoder.yudao.module.infra.util.file.vo.FileUploadVo;
import cn.iocoder.yudao.framework.common.vo.OptionVO;
import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils;
import cn.iocoder.yudao.module.infra.enums.MessageTypeEnum;
import cn.iocoder.yudao.module.member.controller.admin.userpointrecords.vo.UserPointRecordsSaveReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.enums.userpointrecords.UserPointRecordsBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.userpointrecords.UserPointRecordsOpTypeEnum;
import cn.iocoder.yudao.module.member.enums.userwithdraw.WithdrawStatusEnum;
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
import cn.iocoder.yudao.module.member.service.userpointrecords.UserPointRecordsService;
import cn.iocoder.yudao.module.pay.enums.packagec.PayPackageTypeEnum;
import cn.iocoder.yudao.module.system.dal.dataobject.expert.ExpertDO;
import cn.iocoder.yudao.module.system.service.expert.ExpertService;
import cn.iocoder.yudao.module.written.exam.controller.admin.userwrittenexamrecords.vo.UserWrittenExamRecordsSaveReqVO;
import cn.iocoder.yudao.module.written.exam.controller.admin.userwrittenexamrecords.vo.UserWrittenExamRecordsUpdateReqVO;
import cn.iocoder.yudao.module.written.exam.controller.admin.userwrittenexamrecordsdetail.vo.UserWrittenExamRecordsDetailSaveReqVO;
import cn.iocoder.yudao.module.written.exam.controller.app.userwrittenexamrecordsdetail.vo.AppUserWrittenExamRecordsDetailGroupRespVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.AppWrittenExamFinishReqVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.AppWrittenExamPointsScopeRespVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.AppWrittenExamSettingReqVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.AppWrittenExamSettingRespVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.AppWrittenExamStopPushAnswerReqVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.llm.LlmChatReqVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.message.AppWrittenExamAnswerMessageVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.message.AppWrittenExamMessageVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.message.AppWrittenExamQuestionTypeMessageVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.message.AppWrittenExamSummaryMessageVO;
import cn.iocoder.yudao.module.written.exam.controller.app.writtenexam.vo.message.AppWrittenExamUploadFilesMessageVO;
import cn.iocoder.yudao.module.written.exam.dal.dataobject.jobcategories.JobCategoriesDO;
import cn.iocoder.yudao.module.written.exam.dal.dataobject.llm.LlmEventBase;
import cn.iocoder.yudao.module.written.exam.dal.dataobject.llm.LlmEventTypeEnum;
import cn.iocoder.yudao.module.written.exam.dal.dataobject.llm.MessageEvent;
import cn.iocoder.yudao.module.written.exam.dal.dataobject.llm.WorkflowFinishedEvent;
import cn.iocoder.yudao.module.written.exam.dal.dataobject.userwrittenexamrecords.UserWrittenExamRecordsDO;
import cn.iocoder.yudao.module.written.exam.dal.redis.CacheKeyUtil;
import cn.iocoder.yudao.module.written.exam.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.written.exam.enums.ProgramLanguageEnum;
import cn.iocoder.yudao.module.written.exam.enums.QuestionTypeEnum;
import cn.iocoder.yudao.module.written.exam.service.jobcategories.JobCategoriesService;
import cn.iocoder.yudao.module.written.exam.service.llm.LlmChatService;
import cn.iocoder.yudao.module.written.exam.service.userwrittenexamrecords.UserWrittenExamRecordsService;
import cn.iocoder.yudao.module.written.exam.service.userwrittenexamrecordsdetail.UserWrittenExamRecordsDetailService;
import cn.iocoder.yudao.module.written.exam.service.writtenexammessage.WrittenExamMessageService;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.socket.WebSocketSession;
import reactor.core.publisher.Flux;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.written.exam.enums.ErrorCodeConstants.USER_WRITTEN_EXAM_RECORDS_NOT_EXISTS;

@Slf4j
@Service
public class WrittenExamServiceImpl implements WrittenExamService {

    private static Long UPLOAD_FILE_MAX_SIZE = 10 * 1024 * 1024L;
    private static Integer UPLOAD_FILE_MAX_NUM = 3;
    private static Integer OP_TYPE_PARTIAL_SCREENSHOT_ADD_PICTURES = 1;
    private static Map<String, Boolean> stopPushAnswerMap = new HashMap<>();

    @Resource
    private FileUploadUtils fileUploadUtils;
    @Resource
    private WebSocketSessionManager sessionManager;
    @Resource
    private LlmChatService llmChatService;
    @Resource
    private WrittenExamMessageService writtenExamMessageService;
    @Resource
    private UserWrittenExamRecordsService userWrittenExamRecordsService;
    @Resource
    private UserWrittenExamRecordsDetailService userWrittenExamRecordsDetailService;
    @Resource
    private JobCategoriesService jobCategoriesService;
    @Resource
    private ExpertService expertService;
    @Resource
    private MemberUserService memberUserService;
    @Resource
    private UserPointRecordsService userPointRecordsService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Map<String, List<OptionVO>> getEnums() {
        Map<String, List<OptionVO>> enums = new HashMap<>();

        List<OptionVO> optionsWrittenExamTypes = new ArrayList<>();
        for (WrittenExamTypeEnum writtenExamTypeEnum : WrittenExamTypeEnum.values()) {
            optionsWrittenExamTypes.add(new OptionVO(writtenExamTypeEnum.getCode().toString(), writtenExamTypeEnum.getDesc()));
        }
        enums.put("writtenExamTypes", optionsWrittenExamTypes);

        List<OptionVO> optionsQuestionTypes = new ArrayList<>();
        for (QuestionTypeEnum questionTypeEnum : QuestionTypeEnum.values()) {
            optionsQuestionTypes.add(new OptionVO(questionTypeEnum.getCode().toString(), questionTypeEnum.getDesc()));
        }
        enums.put("questionTypes", optionsQuestionTypes);

        List<OptionVO> optionsPayPackageTypes = new ArrayList<>();
        for (PayPackageTypeEnum payPackageTypeEnum : PayPackageTypeEnum.values()) {
            optionsPayPackageTypes.add(new OptionVO(payPackageTypeEnum.getCode().toString(), payPackageTypeEnum.getDesc()));
        }
        enums.put("payPackageTypes", optionsPayPackageTypes);

        List<OptionVO> optionsPointBizTypes = new ArrayList<>();
        for (UserPointRecordsBizTypeEnum userPointRecordsBizTypeEnum : UserPointRecordsBizTypeEnum.values()) {
            optionsPointBizTypes.add(new OptionVO(userPointRecordsBizTypeEnum.getType().toString(), userPointRecordsBizTypeEnum.getName()));
        }
        enums.put("pointBizTypes", optionsPointBizTypes);

        List<OptionVO> optionsWithdrawStatus = new ArrayList<>();
        for (WithdrawStatusEnum withdrawStatusEnum : WithdrawStatusEnum.values()) {
            optionsWithdrawStatus.add(new OptionVO(withdrawStatusEnum.getCode().toString(), withdrawStatusEnum.getDesc()));
        }
        enums.put("withdrawStatus", optionsWithdrawStatus);

        List<OptionVO> optionsProgramLanguages = new ArrayList<>();
        for (ProgramLanguageEnum programLanguageEnum : ProgramLanguageEnum.values()) {
            optionsProgramLanguages.add(new OptionVO(programLanguageEnum.getCode().toString(), programLanguageEnum.getDesc()));
        }
        enums.put("programLanguages", optionsProgramLanguages);

        return enums;
    }

    @Override
    public Boolean desktopStatus(Long userId) {
        List<WebSocketSession> sessionList = (List<WebSocketSession>) sessionManager.getSessionList(UserTypeEnum.MEMBER.getValue(), userId);
        if (sessionList.isEmpty()) {
            return false;
        }
        for (WebSocketSession webSocketSession : sessionList) {
            if (WebSocketFrameworkUtils.DESKTOP.equals(webSocketSession.getAttributes().get(WebSocketFrameworkUtils.ATTRIBUTE_SOURCE))) {
                return true;
            }
        }
        return false;
    }

    @Override
    public AppWrittenExamPointsScopeRespVO pointsScope() {
        AppWrittenExamPointsScopeRespVO appWrittenExamPointsScopeRespVO = new AppWrittenExamPointsScopeRespVO();

        ExpertDO useMinimumPointsExpert = expertService.getUseMinimumPointsExpert();
        appWrittenExamPointsScopeRespVO.setMinimum(getUsePoints(useMinimumPointsExpert.getPoints(), QuestionTypeEnum.SINGLE_CHOICE.getCoefficient(), YesOrNoEnum.NO.getCode()));

        ExpertDO useMaximumPointsExpert = expertService.getUseMaximumPointsExpert();
        appWrittenExamPointsScopeRespVO.setMaximum(getUsePoints(useMaximumPointsExpert.getPoints(), QuestionTypeEnum.PROGRAM_QUESTION.getCoefficient(), YesOrNoEnum.YES.getCode()));

        return appWrittenExamPointsScopeRespVO;
    }

    @Override
    public Boolean settingSet(Long userId, AppWrittenExamSettingReqVO reqVO) {
        String key = CacheKeyUtil.getWrittenExamSettingKey(userId);
        HashOperations<String, Object, Object> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash();
        if (Objects.nonNull(reqVO.getWrittenExamTypeCode())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_WRITTEN_EXAM_TYPE_CODE, reqVO.getWrittenExamTypeCode().toString());
        }
        if (Objects.nonNull(reqVO.getJobCategoriesId())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_JOB_CATEGORIES_ID, reqVO.getJobCategoriesId().toString());
        }
        if (Objects.nonNull(reqVO.getExpertId())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_EXPERT_ID, reqVO.getExpertId().toString());
        }
        if (Objects.nonNull(reqVO.getQuestionTypeCode())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_QUESTION_TYPE_CODE, reqVO.getQuestionTypeCode().toString());
            AppWrittenExamQuestionTypeMessageVO messageVO = new AppWrittenExamQuestionTypeMessageVO();
            messageVO.setQuestionType(reqVO.getQuestionTypeCode());
            writtenExamMessageService.push(userId, MessageTypeEnum.QUESTION_TYPE.getType(), messageVO);
        }
        if (Objects.nonNull(reqVO.getForceScroll())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_FORCE_SCROLL, reqVO.getForceScroll().toString());
        }
        if (Objects.nonNull(reqVO.getTwoColumnMode())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_TWO_COLUMN_MODE, reqVO.getTwoColumnMode().toString());
        }
        if (Objects.nonNull(reqVO.getCodeDetailComments())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_CODE_DETAIL_COMMENTS, reqVO.getCodeDetailComments().toString());
        }
        if (Objects.nonNull(reqVO.getContextAnalysis())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_CONTEXT_ANALYSIS, reqVO.getContextAnalysis().toString());
        }
        if (Objects.nonNull(reqVO.getPlayAnswer())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_PLAY_ANSWER, reqVO.getPlayAnswer().toString());
        }
        if (Objects.nonNull(reqVO.getExpertId()) || Objects.nonNull(reqVO.getQuestionTypeCode())) {
            pushSummaryMessage(userId, reqVO.getConversationId());
        }
        if (Objects.nonNull(reqVO.getProgramLanguage())) {
            stringObjectObjectHashOperations.put(key, CacheKeyUtil.HASH_KEY_PROGRAM_LANGUAGE, reqVO.getProgramLanguage());
        }
        return true;
    }

    private Integer getInteger(Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }
        return Integer.parseInt(obj.toString());
    }

    private Long getLong(Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }
        return Long.parseLong(obj.toString());
    }

    @Override
    public AppWrittenExamSettingRespVO settingGet(Long userId) {
        AppWrittenExamSettingRespVO appWrittenExamSettingRespVO = new AppWrittenExamSettingRespVO();
        String key = CacheKeyUtil.getWrittenExamSettingKey(userId);
        HashOperations<String, Object, Object> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash();
        appWrittenExamSettingRespVO.setWrittenExamTypeCode(getInteger(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_WRITTEN_EXAM_TYPE_CODE)));
        appWrittenExamSettingRespVO.setJobCategoriesId(getLong(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_JOB_CATEGORIES_ID)));
        appWrittenExamSettingRespVO.setExpertId(getLong(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_EXPERT_ID)));
        appWrittenExamSettingRespVO.setQuestionTypeCode(getInteger(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_QUESTION_TYPE_CODE)));
        appWrittenExamSettingRespVO.setForceScroll(getInteger(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_FORCE_SCROLL)));
        appWrittenExamSettingRespVO.setTwoColumnMode(getInteger(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_TWO_COLUMN_MODE)));
        appWrittenExamSettingRespVO.setCodeDetailComments(getInteger(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_CODE_DETAIL_COMMENTS)));
        appWrittenExamSettingRespVO.setContextAnalysis(getInteger(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_CONTEXT_ANALYSIS)));
        appWrittenExamSettingRespVO.setPlayAnswer(getInteger(stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_PLAY_ANSWER)));
        Object programLanguagesHashKey = stringObjectObjectHashOperations.get(key, CacheKeyUtil.HASH_KEY_PROGRAM_LANGUAGE);
        appWrittenExamSettingRespVO.setProgramLanguage(Objects.nonNull(programLanguagesHashKey) ? programLanguagesHashKey.toString() : null);

        log.info("获取配置：userId={}, appWrittenExamSettingRespVO={}", userId, JSONObject.toJSONString(appWrittenExamSettingRespVO));
        return appWrittenExamSettingRespVO;
    }

    @Override
    public String uploadFiles(Long userId, String userIp, String conversationId, MultipartFile[] files, Integer opType) {
        log.info("接收到批量文件上传请求：文件数量={}", files.length);

        FileUploadVo fileUploadVo = fileUploadUtils.upload(files, UPLOAD_FILE_MAX_NUM, UPLOAD_FILE_MAX_SIZE);

        List<String> imageUrls = new ArrayList<>();
        for (String fileUrl : fileUploadVo.fileUrls) {
            imageUrls.add(fileUploadUtils.imagePrefixUrl + fileUrl);
        }

        AppWrittenExamUploadFilesMessageVO messageVO = new AppWrittenExamUploadFilesMessageVO();
        messageVO.setImageUrls(imageUrls);

        if (Objects.equals(opType, OP_TYPE_PARTIAL_SCREENSHOT_ADD_PICTURES)) {
            writtenExamMessageService.push(userId, MessageTypeEnum.PARTIAL_SCREENSHOT_ADD_PICTURES.getType(), messageVO);
            return "";
        }

        writtenExamMessageService.push(userId, MessageTypeEnum.UPLOAD_FILES.getType(), messageVO);

        // 调用Service层批量上传文件到大模型平台，获取文件ID列表
        List<String> fileIds = llmChatService.uploadFiles(fileUploadVo.savedFilePaths);

        // 构建响应
//        LlmFileUploadRespVO respVO = new LlmFileUploadRespVO();
//        respVO.setFileUrls(fileUploadVo.fileUrls);
//        respVO.setFileIds(fileIds);

        log.info("批量文件上传完成：fileIds={}, 本地路径={}", fileIds, fileUploadVo.savedFilePaths);

        LlmChatReqVO reqVO = new LlmChatReqVO();
        reqVO.setConversationId(conversationId);
        reqVO.setFileUrls(fileUploadVo.fileUrls);
        reqVO.setExamImgs(fileIds);
        reqVO.setQuery("请给出答案");

        return pushAnswerMessage(userId, reqVO, userIp);
    }

    /**
     * 获取当前使用的考试类型
     *
     * @param writtenExamTypeCode
     * @return
     */
    public WrittenExamTypeEnum getUseWrittenExamType(Integer writtenExamTypeCode) {
        if (Objects.isNull(writtenExamTypeCode)) {
            throw exception(ErrorCodeConstants.WRITTEN_EXAM_SETTING_NOT_SET_EXAM_TYPE);
        }
        WrittenExamTypeEnum writtenExamTypeEnum = WrittenExamTypeEnum.getByCode(writtenExamTypeCode);
        if (Objects.isNull(writtenExamTypeEnum)) {
            throw exception(cn.iocoder.yudao.module.written.exam.enums.ErrorCodeConstants.WRITTEN_EXAM_TYPE_NOT_EXISTS);
        }
        return writtenExamTypeEnum;
    }

    /**
     * 获取当前使用的专家
     *
     * @param expertId
     * @return
     */
    public ExpertDO getUseExpert(Long expertId) {
        ExpertDO expertDO = expertService.getExpert(expertId);
        if (Objects.isNull(expertDO)) {
            throw exception(cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.EXPERT_NOT_EXISTS);
        }
        return expertDO;
    }

    /**
     * 获取当前使用的题目类型
     *
     * @param questionTypeCode
     * @return
     */
    public QuestionTypeEnum getUseQuestionType(Integer questionTypeCode) {
        if (Objects.isNull(questionTypeCode)) {
            throw exception(ErrorCodeConstants.WRITTEN_EXAM_SETTING_NOT_SET_QUESTION_TYPE);
        }
        QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.getByCode(questionTypeCode);
        if (Objects.isNull(questionTypeEnum)) {
            throw exception(ErrorCodeConstants.QUESTION_TYPE_NOT_EXISTS);
        }
        return questionTypeEnum;
    }

    /**
     * 获取需要使用的积分
     *
     * @param expertPoints            专家积分
     * @param questionTypeCoefficient 题目类型系数
     * @param playAnswer              是否播放答案
     * @return
     */
    public Integer getUsePoints(Integer expertPoints, double questionTypeCoefficient, Integer playAnswer) {
        int usePoints = (int) (expertPoints * questionTypeCoefficient);
        if (Objects.equals(playAnswer, YesOrNoEnum.YES.getCode())) {
            usePoints += 100;
        }
        return usePoints;
    }

    @Override
    public String pushAnswerMessage(Long userId, LlmChatReqVO reqVO, String userIp) {
        log.info("接收到发送消息请求：userId={}, llmChatReqVO={}, userIp={}", userId, JSONObject.toJSONString(reqVO), userIp);
        LocalDateTime startTime = LocalDateTime.now();
        AppWrittenExamSettingRespVO appWrittenExamSettingRespVO = settingGet(userId);
        WrittenExamTypeEnum writtenExamTypeEnum = getUseWrittenExamType(appWrittenExamSettingRespVO.getWrittenExamTypeCode());
        JobCategoriesDO jobCategoriesDO = jobCategoriesService.getJobCategories(appWrittenExamSettingRespVO.getJobCategoriesId());
        ExpertDO expertDO = getUseExpert(appWrittenExamSettingRespVO.getExpertId());
        QuestionTypeEnum questionTypeEnum = getUseQuestionType(appWrittenExamSettingRespVO.getQuestionTypeCode());
        Integer usePoints = getUsePoints(expertDO.getPoints(), questionTypeEnum.getCoefficient(), appWrittenExamSettingRespVO.getPlayAnswer());
        MemberUserDO user = memberUserService.getUser(userId);
        if (user.getPoints() < usePoints) {
            throw exception(cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.USER_POINTS_NOT_ENOUGH);
        }
        try {
            reqVO.setExamType(writtenExamTypeEnum.getValue());
            if (Objects.nonNull(jobCategoriesDO)) {
                reqVO.setPosition(jobCategoriesDO.getCategoryName());
            }
            reqVO.setLanguage(appWrittenExamSettingRespVO.getProgramLanguage());
            reqVO.setQuestionType(questionTypeEnum.getDesc());
            reqVO.setAnswerExpert(expertDO.getName());

            // 调用Service获取Flux流
            Flux<LlmEventBase> flux = llmChatService.sendMessage(reqVO, userIp, reqVO.getQuery());

            // 阻塞式收集所有流式数据，30秒超时
            StringBuilder fullAnswer = new StringBuilder();
            LlmEventBase lastEvent = flux.doOnNext(event -> {
                        // 如果是消息事件，提取答案内容
                        log.info("<UNK>event={}", JSONObject.toJSONString(event));
                        if (event.getType().equals(LlmEventTypeEnum.MESSAGE)) {
                            MessageEvent messageEvent = (MessageEvent) event;
                            Boolean stop = stopPushAnswerMap.get(messageEvent.getMessageId());
                            if (Objects.isNull(stop)) {
                                AppWrittenExamAnswerMessageVO messageVO = new AppWrittenExamAnswerMessageVO();
                                messageVO.setConversationId(messageEvent.getConversationId());
                                messageVO.setMessageId(messageEvent.getMessageId());
                                messageVO.setAnswer(messageEvent.getAnswer());
                                writtenExamMessageService.push(userId, MessageTypeEnum.WRITTEN_EXAM_ANSWER.getType(), messageVO);
                            }
                        } else if (event.getType().equals(LlmEventTypeEnum.WORKFLOW_FINISHED)) {
                            WorkflowFinishedEvent workflowFinishedEvent = (WorkflowFinishedEvent) event;
                            Long userWrittenExamRecordsId = addUserWrittenExamRecords(userId, workflowFinishedEvent);
                            Long userWrittenExamRecordsDetailId = addUserWrittenExamRecordsDetail(userId, reqVO, userWrittenExamRecordsId,
                                    workflowFinishedEvent, jobCategoriesDO, expertDO, appWrittenExamSettingRespVO, startTime);
                            memberUserService.updateUserPoints(userId, -usePoints);
                            addUserPointsRecords(userId, userWrittenExamRecordsDetailId, -usePoints);
                            pushSummaryMessage(userId, workflowFinishedEvent.getConversationId());
                            stopPushAnswerMap.remove(workflowFinishedEvent.getMessageId());
                        }
                    })
                    .blockLast(Duration.ofSeconds(30));
            return fullAnswer.toString();
        } catch (Exception e) {
            log.error("发送消息请求处理失败：{}", e.getMessage(), e);
            return "处理发送消息请求失败：" + e.getMessage();
        }
    }

    private Long addUserWrittenExamRecords(Long userId, WorkflowFinishedEvent workflowFinishedEvent) {
        UserWrittenExamRecordsDO userWrittenExamRecordsDO = userWrittenExamRecordsService.getUserWrittenExamRecordsByConversationId(workflowFinishedEvent.getConversationId());
        Long userWrittenExamRecordsId;
        if (Objects.isNull(userWrittenExamRecordsDO)) {
            UserWrittenExamRecordsSaveReqVO createReqVO = new UserWrittenExamRecordsSaveReqVO();
            createReqVO.setUserId(userId);
            createReqVO.setConversationId(workflowFinishedEvent.getConversationId());
            userWrittenExamRecordsId = userWrittenExamRecordsService.createUserWrittenExamRecords(createReqVO);
        } else {
            userWrittenExamRecordsId = userWrittenExamRecordsDO.getId();
        }
        return userWrittenExamRecordsId;
    }

    private Long addUserWrittenExamRecordsDetail(Long userId, LlmChatReqVO reqVO, Long userWrittenExamRecordsId,
                                                 WorkflowFinishedEvent workflowFinishedEvent, JobCategoriesDO jobCategoriesDO, ExpertDO expertDO,
                                                 AppWrittenExamSettingRespVO appWrittenExamSettingRespVO, LocalDateTime startTime) {
        UserWrittenExamRecordsDetailSaveReqVO createReqVO = new UserWrittenExamRecordsDetailSaveReqVO();
        createReqVO.setUserId(userId);
        createReqVO.setRecordsId(userWrittenExamRecordsId);
        createReqVO.setMessageId(workflowFinishedEvent.getMessageId());
        createReqVO.setFileIds(CollectionUtils.convertListToString(reqVO.getExamImgs()));
        createReqVO.setExamType(appWrittenExamSettingRespVO.getWrittenExamTypeCode());
        if (Objects.nonNull(jobCategoriesDO)) {
            createReqVO.setJobCategoriesId(jobCategoriesDO.getId());
            createReqVO.setJobCategoriesName(jobCategoriesDO.getCategoryName());
        }
        createReqVO.setExpertId(expertDO.getId());
        createReqVO.setExpertName(expertDO.getName());
        createReqVO.setConsumedPoints(expertDO.getPoints());
        createReqVO.setQuestionType(appWrittenExamSettingRespVO.getQuestionTypeCode());
        createReqVO.setFileUrls(CollectionUtils.convertListToString(reqVO.getFileUrls()));
        createReqVO.setQuestionText(reqVO.getQuery());
        createReqVO.setQuestionAnswer(workflowFinishedEvent.getData().getOutputs().get("answer").toString());
        createReqVO.setForceScroll(appWrittenExamSettingRespVO.getForceScroll());
        createReqVO.setTwoColumnMode(appWrittenExamSettingRespVO.getTwoColumnMode());
        createReqVO.setCodeDetailComments(appWrittenExamSettingRespVO.getCodeDetailComments());
        createReqVO.setContextAnalysis(appWrittenExamSettingRespVO.getContextAnalysis());
        createReqVO.setPlayAnswer(appWrittenExamSettingRespVO.getPlayAnswer());
        createReqVO.setStartTime(startTime);
        return userWrittenExamRecordsDetailService.createUserWrittenExamRecordsDetail(createReqVO);
    }

    private void addUserPointsRecords(Long userId, Long bizId, Integer points) {
        UserPointRecordsSaveReqVO userPointRecordsSaveReqVO = new UserPointRecordsSaveReqVO();
        userPointRecordsSaveReqVO.setUserId(userId);
        userPointRecordsSaveReqVO.setOpType(UserPointRecordsOpTypeEnum.SUBTRACT.getCode());
        userPointRecordsSaveReqVO.setBizType(UserPointRecordsBizTypeEnum.WRITTEN_EXAM_USE.getType());
        userPointRecordsSaveReqVO.setBizId(bizId);
        userPointRecordsSaveReqVO.setPoints(points);
        userPointRecordsSaveReqVO.setTotalPoints(memberUserService.getUser(userId).getPoints());
        userPointRecordsSaveReqVO.setCreator(userId.toString());
        userPointRecordsService.createUserPointRecords(userPointRecordsSaveReqVO);
    }

    @Override
    public Boolean pushSummaryMessage(Long userId, String conversationId) {
        AppWrittenExamSummaryMessageVO messageVO = new AppWrittenExamSummaryMessageVO();

        Integer points = memberUserService.getUser(userId).getPoints();
        messageVO.setRemainingPoints(points);

        messageVO.setConsumePoints(0);
        if (StringUtils.hasLength(conversationId)) {
            UserWrittenExamRecordsDO userWrittenExamRecordsDO = userWrittenExamRecordsService.getUserWrittenExamRecordsByConversationId(conversationId);
            if (Objects.nonNull(userWrittenExamRecordsDO)) {
                Long recordsId = userWrittenExamRecordsDO.getId();
                Map<Long, AppUserWrittenExamRecordsDetailGroupRespVO> map = userWrittenExamRecordsDetailService.groupByRecordsId(Arrays.asList(recordsId));
                messageVO.setConsumePoints(map.get(recordsId).getConsumedPoints());
            }
        }

        AppWrittenExamSettingRespVO appWrittenExamSettingRespVO = settingGet(userId);
        ExpertDO expertDO = getUseExpert(appWrittenExamSettingRespVO.getExpertId());
        QuestionTypeEnum questionTypeEnum = getUseQuestionType(appWrittenExamSettingRespVO.getQuestionTypeCode());
        Integer usePoints = getUsePoints(expertDO.getPoints(), questionTypeEnum.getCoefficient(), appWrittenExamSettingRespVO.getPlayAnswer());
        messageVO.setCanAnswereQuestionCount((int) (points / usePoints));

        writtenExamMessageService.push(userId, MessageTypeEnum.WRITTEN_EXAM_SUMMARY.getType(), messageVO);

        return true;
    }

    @Override
    public Boolean stopPushAnswer(AppWrittenExamStopPushAnswerReqVO reqVO) {
        stopPushAnswerMap.put(reqVO.getMessageId(), true);
        return true;
    }

    @Override
    public Boolean finish(AppWrittenExamFinishReqVO reqVO) {
        UserWrittenExamRecordsDO userWrittenExamRecordsDO = userWrittenExamRecordsService.getUserWrittenExamRecordsByConversationId(reqVO.getConversationId());
        if (Objects.isNull(userWrittenExamRecordsDO)) {
            throw exception(USER_WRITTEN_EXAM_RECORDS_NOT_EXISTS);
        }

        UserWrittenExamRecordsUpdateReqVO updateReqVO = new UserWrittenExamRecordsUpdateReqVO();
        updateReqVO.setId(userWrittenExamRecordsDO.getId());
        updateReqVO.setEndTime(LocalDateTime.now());
        userWrittenExamRecordsService.updateUserWrittenExamRecords(updateReqVO);

        writtenExamMessageService.push(reqVO.getUserId(), MessageTypeEnum.WRITTEN_EXAM_FINISH.getType(), new AppWrittenExamMessageVO());

        return true;
    }

}
